package org.devgframwork.es5.elasticserach;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import io.searchbox.annotations.JestId;
import io.searchbox.client.JestClient;
import io.searchbox.client.JestResult;
import io.searchbox.core.*;
import io.searchbox.core.search.aggregation.MetricAggregation;
import io.searchbox.indices.DeleteIndex;
import io.searchbox.indices.IndicesExists;
import io.searchbox.indices.type.TypeExist;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.search.builder.SearchSourceBuilder;

import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @Description
 * @auther dh
 * @date 2021/12/23 16:44
 */

@Slf4j
public class EsSearch {

    public static JestClient jestClient = null;

    public static JestClient getJestClient() {
        return EsSearch.jestClient;
    }

    private static boolean showLog = false;

    public static void setShowLog(boolean showLog) {
        EsSearch.showLog = showLog;
    }

    public static String transferLongToDate(String dateFormat, Long millSec) {
        SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
//        String millSecStr = millSec + "000";
//        Date date = new Date(Long.valueOf(millSecStr));
        Date date = new Date(millSec);
        String resDate = sdf.format(date);
        return resDate;
    }

    public static <T> T getEsResultEntity(Map<String, Object> map, Class<T> sourceType) throws Exception {
        T t = sourceType.newInstance();
        Field[] fields = sourceType.getDeclaredFields();
        for (Field field : fields) {
            EsCloumn esCloumn = field.getAnnotation(EsCloumn.class);
            if (esCloumn != null) {
                field.setAccessible(true);
                String esName = esCloumn.name();
                boolean esArray = esCloumn.array();
                boolean showResult = esCloumn.showResult();
                if (!showResult) {
                    continue;
                }
                Object esValue = null;
                if (esArray) {
                    String arrayValue = JSON.toJSONString(map.get(esName));
                    Class<T> arrClazz = esCloumn.clazz();
                    if (null != arrayValue && !"".equals(arrayValue.trim())) {
                        JSONArray jsonArray = JSON.parseArray(arrayValue);
                        if (null == jsonArray || jsonArray.size() <= 0) {
                            continue;
                        }
                        int arraySize = jsonArray.size();
                        T[] arrDatas = (T[]) Array.newInstance(arrClazz, arraySize); //创建一个数组
                        for (int i = 0; i < arraySize; i++) {
                            T arrData = arrClazz.newInstance();
                            Field[] esFields = arrClazz.getDeclaredFields();
                            Object value = jsonArray.get(i);
                            if (value instanceof JSONObject) {
                                JSONObject jsonObject = (JSONObject) value;
                                for (Field arrfield : esFields) {
                                    arrfield.setAccessible(true);
                                    Object arrEsValue = getEsValue(arrfield, jsonObject);
                                    Field arrEntityField = arrClazz.getDeclaredField(arrfield.getName());
                                    arrEntityField.setAccessible(true);
                                    arrEntityField.set(arrData, arrEsValue);
                                }
                            }
                            arrDatas[i] = (T) value;
                        }
                        esValue = arrDatas;
                    }
                } else {
                    esValue = getEsValue(field, map);
                }
                Field entityField = t.getClass().getDeclaredField(field.getName());
                entityField.setAccessible(true);
                entityField.set(t, esValue);
                continue;
            }
        }
        return t;
    }

    public static Object getEsValue(Field field, Map<String, Object> map) {
        Object esValue = null;
        EsCloumn esCloumn = field.getAnnotation(EsCloumn.class);
        if (esCloumn != null) {
            String dateFormat = esCloumn.dateformat();
            String esName = esCloumn.name();
            String esTypeName = field.getType().getName();
            switch (esTypeName) {
                case "java.lang.Integer":
                    esValue = map.get(esName) == null ? null : Integer.valueOf(map.get(esName).toString());
                    break;
                case "java.lang.Long":
                    esValue = map.get(esName) == null ? null : Long.valueOf(map.get(esName).toString());
                    break;
                case "java.lang.String":
                    esValue = map.get(esName) == null ? null : String.valueOf(map.get(esName));
                    if (null != dateFormat && !"".equals(dateFormat.trim())) {
                        esValue = (esValue == null ? null : transferLongToDate(dateFormat, Long.valueOf(esValue.toString())));
                    }
                    break;
                case "java.lang.Double":
                    esValue = map.get(esName) == null ? null : Double.valueOf(map.get(esName).toString());
                    break;
                case "java.math.BigDecimal":
                    esValue = map.get(esName) == null ? null : new BigDecimal(map.get(esName).toString());
                    break;
                default:
                    esValue = map.get(esName) == null ? null : String.valueOf(map.get(esName));
                    if (null != dateFormat && !"".equals(dateFormat.trim())) {
                        esValue = (esValue == null ? null : transferLongToDate(dateFormat, Long.valueOf(esValue.toString())));
                    }
                    break;
            }
        }
        return esValue;
    }


    public static <T> Map<String, Object> getEsValue(T t) throws Exception {
        Map<String, Object> esValueMap = new HashMap<>();
        Field[] fields = t.getClass().getDeclaredFields();
        for (Field field : fields) {
            EsCloumn esCloumn = field.getAnnotation(EsCloumn.class);
            if (esCloumn == null) {
                continue;
            }
            field.setAccessible(true);
            String esName = esCloumn.name();
            Field entityField = t.getClass().getDeclaredField(field.getName());
            entityField.setAccessible(true);
            Object val = entityField.get(t);
            esValueMap.put(esName, val);
        }
        return esValueMap;
    }

    public static <T> String getJestId(T t) throws Exception {
        Field[] fields = t.getClass().getDeclaredFields();
        String id = null;
        for (Field field : fields) {
            JestId jestId = field.getAnnotation(JestId.class);
            if (jestId == null) {
                continue;
            }
            Field entityField = t.getClass().getDeclaredField(field.getName());
            entityField.setAccessible(true);
            Object val = entityField.get(t);
            if (null != val) {
                id = val.toString();
            }
            break;
        }
        return id;
    }

    public static SearchResult getSearchResult(SearchSourceBuilder searchSourceBuilder, String index, String type) throws IOException {
//		searchSourceBuilder.trackScores(true);  // 这个字段 表示 排序后 是否还按权重查询
        String query = searchSourceBuilder.toString();
        if (showLog) {
            log.info("elasticsearch query:{}", query);
        }
        Search search = new Search.Builder(query).addIndex(index).addType(type).build();
        SearchResult searchResult = null;
        try {
            searchResult = jestClient.execute(search);
        } catch (Exception ex) {
            log.error("search-exception:", ex);
            try {
                searchResult = jestClient.execute(search);
            } catch (Exception e) {
                log.error("search-exception1:", ex);
            }
        }
        return searchResult;
    }


    public static JsonArray getSearchHits(SearchSourceBuilder searchSourceBuilder, String index, String type) throws IOException {
        SearchResult searchResult = getSearchResult(searchSourceBuilder, index, type);
        JsonArray jsonArray = searchResult.getJsonObject().get("hits").getAsJsonObject().get("hits").getAsJsonArray();
        return jsonArray;
    }

    public static long getTotalHits(SearchSourceBuilder searchSourceBuilder, String index, String type) throws IOException {
        SearchResult searchResult = getSearchResult(searchSourceBuilder, index, type);
        return searchResult.getTotal();
    }

    public static MetricAggregation getAggregations(SearchSourceBuilder searchSourceBuilder, String index, String type) throws IOException {
        SearchResult searchResult = getSearchResult(searchSourceBuilder, index, type);
        return searchResult.getAggregations();
    }

    public static <T> EsResult<T> getEsResult(SearchSourceBuilder searchSourceBuilder, Integer pageNum, Integer pageSize,
                                              String index, String type, Class<T> sourceType) throws Exception {
        searchSourceBuilder.from((pageNum - 1) * pageSize);
        searchSourceBuilder.size(pageSize);
        return getEsResult(searchSourceBuilder, index, type, sourceType);
    }

    public static <T> EsResult<T> getEsResult(SearchSourceBuilder searchSourceBuilder, String index, String type, Class<T> sourceType) throws Exception {
        List<T> resultList = new ArrayList<>();
        SearchResult searchResult = getSearchResult(searchSourceBuilder, index, type);
        JsonArray jsonArray = searchResult.getJsonObject().get("hits").getAsJsonObject().get("hits").getAsJsonArray();
        if (null == jsonArray || jsonArray.size() <= 0) {
            return EsResult.getEsResult(resultList, searchResult);
        }
//		Gson gson = new Gson();
        Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Object.class, new EsObjectTypeAdapter()).create();
        for (int i = 0; i < jsonArray.size(); i++) {
            JsonObject jsonObject = jsonArray.get(i).getAsJsonObject().get("_source").getAsJsonObject();
            Map<String, Object> resultMap = gson.fromJson(jsonObject, Map.class);
            T result = getEsResultEntity(resultMap, sourceType);
            resultList.add(result);
        }
        return EsResult.getEsResult(resultList, searchResult);
    }

    public static EsResult<Map<String, Object>> getEsResult(SearchSourceBuilder searchSourceBuilder, Integer pageNum, Integer pageSize,
                                                            String index, String type) throws Exception {
        searchSourceBuilder.from((pageNum - 1) * pageSize);
        searchSourceBuilder.size(pageSize);
        return getEsResult(searchSourceBuilder, index, type);
    }

    public static EsResult<Map<String, Object>> getEsResult(SearchSourceBuilder searchSourceBuilder, String index, String type) throws Exception {
        SearchResult searchResult = getSearchResult(searchSourceBuilder, index, type);
        JsonArray jsonArray = searchResult.getJsonObject().get("hits").getAsJsonObject().get("hits").getAsJsonArray();
        List<Map<String, Object>> resultList = new ArrayList<>();
//		Gson gson = new Gson();
        Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Object.class, new EsObjectTypeAdapter()).create();
        for (int i = 0; i < jsonArray.size(); i++) {
            JsonObject jsonObject = jsonArray.get(i).getAsJsonObject().get("_source").getAsJsonObject();
            Map<String, Object> result = gson.fromJson(jsonObject, Map.class);
            resultList.add(result);
        }
        return EsResult.getEsResult(resultList, searchResult);
    }

    public static <T> boolean updateIndexDocBulk(String indexName, String indexType, List<T> list) throws Exception {
        Bulk.Builder bulk = new Bulk.Builder().defaultIndex(indexName).defaultType(indexType);
        for (T t : list) {
            //如未设置索引唯一id值，则唯一id会默认生成，索引操作为添加操作
            Map<String, Object> data = getEsValue(t);
            Index index = new Index.Builder(data).build();
            bulk.addAction(index);
        }
        BulkResult br = null;
        boolean boll = false;
        try {
            br = jestClient.execute(bulk.build());
            boll = br.isSucceeded();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return boll;
    }

    public static <T> boolean updateIndexDoc(String indexName, String indexType, String id, T t) throws Exception {
        if (null == id || id.trim().equals("")) {
            return false;
        }
        Map<String, Object> data = getEsValue(t);
        Index index = new Index.Builder(data).index(indexName).type(indexType).id(id).refresh(true).build();
        boolean bool = false;
        try {
            JestResult jr = jestClient.execute(index);
            bool = jr.isSucceeded();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bool;
    }


    public static <T> boolean updateIndexDoc(String indexName, String indexType, T t) throws Exception {
        return updateIndexDoc(indexName, indexType, getJestId(t), t);
    }

	/*String builderStr = "{" +
				" \"script\": { " +
				"\"source\": \"ctx._source['" + "black_white_type" + "']='" + "W" + "'\" " +
				"}," +
				"\"query\": {" +
				"\"match\": {" +
				"\"activity_id\": \"" + "1" + "\"" +
				"}" +
				"}" +
				"}";
		log.info("updateByScript builderStr:{}", builderStr);
		UpdateByQuery updateByQuery = new UpdateByQuery.Builder(builderStr).addIndex(indexName).addType(indexType).build();
		UpdateByQueryResult result = jestClient.execute(updateByQuery);
		long updatedCount = result.getUpdatedCount();
		if (result.isSucceeded() && updatedCount != 0L) {
			return true;
		}
		return false;
	*/

    public static <T> boolean updateById(String indexName, String indexType, String id, boolean upsert, T t) throws Exception {
        if (null == id || id.trim().equals("")) {
            return false;
        }
        Map<String, Object> data = getEsValue(t);
        String json = JSONObject.toJSONString(data);
        String builderStr = null;
        if (upsert) {
            builderStr = "{\"doc\":" + json + ",\"doc_as_upsert\":true}";
        } else {
            builderStr = "{\"doc\":" + json + ",\"doc_as_upsert\":false}";
        }
        log.info("upsetById builderStr:{}", builderStr);
//		upsetDoc builderStr:{"doc":{"select_type":"item5"},"doc_as_upsert":true}
        Update index = new Update.Builder(builderStr).index(indexName).type(indexType).id(id).refresh(true).build();
        boolean bool = false;
        try {
            JestResult jr = jestClient.execute(index);
            bool = jr.isSucceeded();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bool;
    }

    public static <T> boolean upsetDoc(String indexName, String indexType, String id, T t) throws Exception {
        return updateById(indexName, indexType, id, true, t);
    }

    public static <T> boolean upsetDoc(String indexName, String indexType, T t) throws Exception {
        return upsetDoc(indexName, indexType, getJestId(t), t);
    }

    public static <T> boolean updateDoc(String indexName, String indexType, String id, T t) throws Exception {
        return updateById(indexName, indexType, id, false, t);
    }

    public static <T> boolean updateDoc(String indexName, String indexType, T t) throws Exception {
        return updateDoc(indexName, indexType, getJestId(t), t);
    }

    public static boolean deleteDoc(String indexName, String indexType, String id) throws Exception {
        Delete index = new Delete.Builder(id).index(indexName).type(indexType).refresh(true).build();
        boolean bool = false;
        try {
            JestResult jestResult = jestClient.execute(index);
            bool = jestResult.isSucceeded();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bool;
    }

    public static boolean indicesExists(String indexName) {
        IndicesExists indicesExists = new IndicesExists.Builder(indexName).build();
        boolean bool = false;
        try {
            JestResult jestResult = jestClient.execute(indicesExists);
            bool = jestResult.isSucceeded();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bool;
    }

    public static boolean indicesTypeExists(String indexName, String indexType) {
        TypeExist typeExist = new TypeExist.Builder(indexName).addType(indexType).build();
        boolean bool = false;
        try {
            JestResult jestResult = jestClient.execute(typeExist);
            bool = jestResult.isSucceeded();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bool;
    }


    /**
     * 删除索引
     */
    public static boolean deleteIndex(String indexName) {
        boolean bool = false;
        try {
            JestResult result = jestClient.execute(new DeleteIndex.Builder(indexName).build());
            bool = result.isSucceeded();
            log.info("delete result state:{}", bool);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bool;
    }
}
